/**
 * 编译器
 */
class Compiler {
    constructor (vm) {
        this.el = vm.$el
        this.vm = vm
        this.compile(this.el)
    }
    /**
     * 编译魔板， 处理文本节点和元素节点
     * @param {*} el 节点
     */
    compile (el) {
        const childNodes = el.childNodes // 伪数组
        Array.from(childNodes).forEach(node => {
            if (this.isTextNode(node)) {
                // 处理文本节点
                this.compileText(node)
            } else if (this.isElementNode(node)) {
                // 处理元素节点
                this.comileElement(node)
            }
             // 判断node节点，是否有子节点，如果有子节点，要递归调用compile
            if (node.childNodes && node.childNodes.length) {
                this.compile(node)
            }
        })
    }
    /**
     * 编译元素节点， 处理指令
     * @param {*} node 元素节点
     */
    comileElement(node) {
        const attributes = node.attributes // 伪数组
        // 遍历所有的属性节点
          Array.from(node.attributes).forEach(attr => {
            // 判断属性是否是指令
            let attrName = attr.name
            if (this.isDirective(attrName)) {
                // v-text => text
                attrName = attrName.substr(2)
                let key = attr.value // 属性值
                if (attrName.indexOf(':') !== -1) {
                    attrName = attrName.substr(3)
                    this.eventHandler(node, key, attrName)
                } else { 
                    this.update(node, key, attrName)
                }
             }
        })
    }
    /**
     * v-on事件处理
     * @param {*} node 节点
     * @param {*} key 属性
     * @param {*} attrName 事件名称
     * @return {*}
     */
    eventHandler(node, key, attrName) { 
        node.addEventListener(attrName, () => { 
            eval(key)
        })
    }
    /**
     * 编译解析节点的指令， 并把指令解析出对应的值
     * @param {*} node 节点
     * @param {*} key  属性值对应的字段
     * @param {*} attrName 属性名称， 用于拼接后续的方法名
     */
    update(node, key, attrName) {
        let updateFn = this[`${attrName}Updater`] // 方法名称
        updateFn && updateFn.call(this, node, this.vm[key], key) // 调用， call改变this指向
    }
    /**
     * v-on
     * @param {*} node
     * @param {*} cb 回调函数
     * @param {*} key
     * @return {void}
     */
    onUpdater(node, cb, key) { 
        node.addEventListener()
    }
    /**
     * v-html 解析
     * @param {*} node 节点
     * @param {string} html 文本html
     * @param {string} key 属性
     * @return {void}
     */
    htmlUpdater(node, html, key) { 
        node.innerHTML = html
        new Watcher(this.vm, key, (newVal) => { 
            node.innerHTML = newVal
        })
    }
    /**
     * 处理v-text指令
     * @param {*} node 节点
     * @param {*} value 文本内容
     * @param {*} key 创建Watcher所需
     */
    textUpdater(node, value, key) { 
        node.textContent = value // 初次渲染
        new Watcher(this.vm, key, (newValue) => { 
            node.textContent = newValue
        })
    }
    /**
     * 处理v-model指令
     * @param {*} node 
     * @param {*} value 
     * @param {*} key  创建Watcher所需
     */
    modelUpdater(node, value, key) { 
        node.value = value // 初次渲染
        new Watcher(this.vm, key, (newVal) => { 
            node.value = newVal
        })
        // 双向绑定
        node.addEventListener('input', () => {
            this.vm[key] = node.value
        })
    }

    /**
     * 编译文本节点， 处理插值表达式
     *  @param {*} node 元素节点
     */
    compileText (node) {
        let reg = /\{\{(.+?)\}\}/ // 正则
        const content = node.textContent // 文本内容
        if (reg.test(content)) {
            let key = RegExp.$1.trim()
            node.textContent = content.replace(reg, this.vm[key]) // 初次渲染

            // 创建watcher对象，当数据改变更新视图, key对应的值发生改变时， 会调用回调函数
            new Watcher(this.vm, key, (newValue) => { 
                node.textContent = newValue
            })
        } 
    }
    /**
     * 判断元素属性是否是指令
     * @param {*} attrName 属性名称
     */
    isDirective (attrName) {
        return attrName.startsWith('v-')
    }
    /**
     * 判断节点是否是文本节点
     * @param {*} node  节点
     */
    isTextNode (node) {
        return node.nodeType === 3
    }
    /**
     * 判断节点是否是元素节点
     * @param {*} node 节点
     */
    isElementNode (node) {
        return node.nodeType === 1
    }
}